package com.yf.utils;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.MurmurHash3;

import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.util.*;

public class Utils {
    public static boolean isNullOrEmpty(String s) {
        return s == null || s.length() == 0;
    }
    public static String digestPasswd(String origin) {
        byte[] o = origin.getBytes();
        long[] hash = MurmurHash3.hash128(o);
        byte[] r = new byte[16];

        r[0] = (byte) (hash[0]);
        r[1] = (byte) (hash[0] >> 8);
        r[2] = (byte) (hash[0] >> 16);
        r[3] = (byte) (hash[0] >> 24);
        r[4] = (byte) (hash[0] >> 32);
        r[5] = (byte) (hash[0] >> 40);
        r[6] = (byte) (hash[0] >> 48);
        r[7] = (byte) (hash[0] >> 56);

        r[8] = (byte) (hash[1]);
        r[9] = (byte) (hash[1] >> 8);
        r[10] = (byte) (hash[1] >> 16);
        r[11] = (byte) (hash[1] >> 2);
        r[12] = (byte) (hash[1] >> 32);
        r[13] = (byte) (hash[1] >> 40);
        r[14] = (byte) (hash[1] >> 48);
        r[15] = (byte) (hash[1] >> 56);

        return Base64.encodeBase64String(r);
    }
    public static Timestamp currentTime() {
        return new Timestamp(System.currentTimeMillis());
    }

    private static final String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    public static final int regCodeLen = 16;
    public static String genRegCode() {
        char[] code = new char[regCodeLen];
        int m = chars.length();
        for (int i = 0; i < regCodeLen; i++) {
            int j = (int) (Math.random() * m);
            code[i] = chars.charAt(j);
        }
        return new String(code);
    }

    public static String randomStr(int n) {
        n = n <= 0 ? 1 : n;
        char[] code = new char[n];
        int m = chars.length();
        for (int i = 0; i < n; i++) {
            int j = (int) (Math.random() * m);
            code[i] = chars.charAt(j);
        }
        return new String(code);
    }

    public static final String o1 = "sc";
    public static final String o2 = "p";
    public static final String o3 = "ot";
    public static final String o4 = "w";

    public static final String defaultAgent = "default_&^";

    public static Map<String, String> encodeUserId(int id) {
        byte o1 = (byte) (id & 0xff);
        id >>>= 8;
        byte o2 = (byte) (id & 0xff);
        id >>>= 8;
        byte o3 = (byte) (id & 0xff);
        id >>>= 8;
        byte o4 = (byte) (id & 0xff);

        byte m = 0;
        Map<String, String> token = new HashMap<>(8);
        String s1 = Utils.randomStr(3);
        m = s1.getBytes()[2];
        token.put(Utils.o1, String.format("%s%02x", s1, (byte) (o1 ^ m)));

        s1 = Utils.randomStr(7);
        m = s1.getBytes()[6];
        token.put(Utils.o2, String.format("%s%02x", s1, (byte) (o2 ^ m)));

        s1 = Utils.randomStr(2);
        m = s1.getBytes()[1];
        token.put(Utils.o3, String.format("%s%02x", s1, (byte) (o3 ^ m)));

        s1 = Utils.randomStr(5);
        m = s1.getBytes()[4];
        token.put(Utils.o4, String.format("%s%02x", s1, (byte) (o4 ^ m)));

        token.put("ct", String.valueOf(Utils.currentTime().getTime()));

        return token;
    }


    public static String encodeInt(int id) {
        byte o1 = (byte) (id & 0xff);
        id >>>= 8;
        byte o2 = (byte) (id & 0xff);
        id >>>= 8;
        byte o3 = (byte) (id & 0xff);
        id >>>= 8;
        byte o4 = (byte) (id & 0xff);

        StringBuilder ret = new StringBuilder(25);
        byte m;

        String s1 = Utils.randomStr(3);
        m = s1.getBytes()[2];
        ret.append(String.format("%s%02x", s1, (byte) (o1 ^ m)));

        s1 = Utils.randomStr(7);
        m = s1.getBytes()[6];
        ret.append(String.format("%s%02x", s1, (byte) (o2 ^ m)));

        s1 = Utils.randomStr(2);
        m = s1.getBytes()[1];
        ret.append(String.format("%s%02x", s1, (byte) (o3 ^ m)));

        s1 = Utils.randomStr(5);
        m = s1.getBytes()[4];
        ret.append(String.format("%s%02x", s1, (byte) (o4 ^ m)));

        return ret.toString();
    }

    private static byte trans(byte o1, byte o2) {
        if (o1 <= 57) {
            o1 -= 48;
        } else {
            o1 -= 87;
        }

        o1 <<= 4;

        if (o2 <= 57) {
            o2 -= 48;
        } else {
            o2 -= 87;
        }

        return (byte) (o1 + o2);
    }

    public static int decodeUserId(String token) {
        if (token == null) {
            return 0;
        }
        // woW 50  clbJdoh 68  D0 30  2l7Lm 6d
        final byte[] bytes = token.getBytes();
        int id = 0;
        if (bytes.length == 25) {
            byte m, c;
            int n, l1, l2, l3, l4;
            m = bytes[2];
            c = trans(bytes[3], bytes[4]);
            n =  m ^ c;
            l1 = 0XFFFFFF00 | n;

            m = bytes[11];
            c = trans(bytes[12], bytes[13]);
            n = m ^ c;
            n <<= 8;
            l2 = 0xFFFF00FF | n;

            m = bytes[15];
            c = trans(bytes[16], bytes[17]);
            n = m ^ c;

            n <<= 16;
            l3 = 0xFF00FFFF | n;

            m = bytes[22];
            c = trans(bytes[23], bytes[24]);
            n = m ^ c;

            n <<= 24;
            l4 = 0x00FFFFFF | n;

            id = l1 & l2 & l3 & l4;
        }
        return id;
    }

    public static byte[] mixData(int k1, int k2, byte[] data) {
        int m = k1 ^ k2;
        byte l1 = (byte) (m & 0xFF);
        byte l2 = (byte) ((m >> 8) & 0xFF);
        byte l3 = (byte) ((m >> 16) & 0xFF);
        byte l4 = (byte) ((m >> 24) & 0xFF);
        int i = 0;
        int j = 0;
        while (i <= data.length - 4) {
            data[j++] = (byte) (l1 ^ data[i]);
            data[j++] = (byte) (l2 ^ data[i+1]);
            data[j++] = (byte) (l3 ^ data[i+2]);
            data[j++] = (byte) (l4 ^ data[i+3]);
            i += 4;
        }
        while (i < data.length) {
            data[j++] = (byte) (l1 ^ data[i]);
            i++;
        }
        return data;
    }

    public static byte[] mixData(int k1, int k2, String data){
        return mixData(k1, k2, data.getBytes());
    }
    public static int getRandInt() {
        Random rand = new Random();
        byte[] m = new byte[4];
        rand.nextBytes(m);

        int l1 = m[0] | 0xffffff00;
        int l2 = m[1] | 0xffff00ff;
        int l3 = m[2] | 0xff00ffff;
        int l4 = m[3] | 0x00ffffff;

        return l1 & l2 & l3 & l4;
    }


    public static <T> Map<String, String> encodeObj(int m, Class<T> clz, T obj) {
        byte l1 = (byte) (m & 0xFF);
        byte l2 = (byte) ((m >> 8) & 0xFF);
        byte l3 = (byte) ((m >> 16) & 0xFF);
        byte l4 = (byte) ((m >> 24) & 0xFF);

//        Class<ExamBean> clz = ExamBean.class;
        StringBuilder header = new StringBuilder(64);
        List<Byte> buff = new ArrayList<>(256);
        final Field[] fields = clz.getDeclaredFields();
        for (Field f: fields) {
            final Class<?> type = f.getType();
            try {
                f.setAccessible(true);
                Object o = f.get(obj);
                if (o == null) {
                    continue;
                }
                String v;
                if (type.equals(String.class)) {
                    v = (String) o;
                } else if (type.equals(Integer.class)) {
                    v = String.valueOf(o);
                } else if (type.equals(Long.class)) {
                    v = String.valueOf(o);
                } else if (type.equals(Short.class)){
                    v = String.valueOf(o);
                } else {
                    continue;
                }
                if (v == null || v.length() == 0) {
                    continue;
                }
                final byte[] bytes = v.getBytes();
                header.append(String.format("%s:%d ",f.getName(), bytes.length));
                int i = 0;
                while (i <= bytes.length - 4) {
                    buff.add((byte) (l1 ^ bytes[i]));
                    buff.add((byte) (l2 ^ bytes[i+1]));
                    buff.add((byte) (l3 ^ bytes[i+2]));
                    buff.add((byte) (l4 ^ bytes[i+3]));
                    i += 4;
                }
                while (i < bytes.length) {
                    buff.add((byte) (l1 ^ bytes[i]));
                    i++;
                }
            } catch (IllegalAccessException ignore) {
                //
            }
        }
        if (buff.size() == 0) {
            return null;
        }
        byte [] buffer = new byte[buff.size()];
        for (int i = 0; i < buff.size(); i++) {
            buffer[i] = buff.get(i);
        }
        if (header.length() > 0) {
            header.deleteCharAt(header.length() - 1);
        }
        Map<String, String> ret = new HashMap<>(3);
        byte[] encodedHeader = Utils.mixData(m, 0, header.toString());
        ret.put("rt", Base64.encodeBase64String(encodedHeader));
        ret.put("ct", Base64.encodeBase64String(buffer));
        ret.put("lk", Utils.encodeInt(m));
        return ret;
    }

    public static Map<String, String> encodeString(byte[] data) {
        int k = getRandInt();
        final byte[] bytes = mixData(k, 0, data);
        final String s = Base64.encodeBase64String(bytes);
        final String lk = encodeInt(k);
        Map<String, String> ret = new HashMap<>(2);
        ret.put("lk", lk);
        ret.put("ct", s);
        return ret;
    }

    public static Map<String, String> encodeString(String data) {
        if (data == null || data.length() == 0) {
            return null;
        }
        return encodeString(data.getBytes());
    }

    public static String genValidCode(int digits) {
        Random rand = new Random();
        if (digits <= 0 || digits > 20) {
            digits = 6;
        }
        StringBuilder ans = new StringBuilder(digits);
        for (int i = 0; i < digits; i++) {
            ans.append(rand.nextInt(10));
        }
        return ans.toString();
    }
}
